package org.iweb.html.impl.basic;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.iweb.html.api.AbstractHtmlElement;
import org.iweb.html.api.JFunction;
import org.iweb.html.api.JScript;
import org.iweb.html.api.Listener;

public class Form extends AbstractHtmlElement {

	private static final long serialVersionUID = -4664966548910870419L;

	private FormBean formBean;
	private Input submit;
	private List<String> hiddens;
	private List<String> required;
	private Span messages;
	private boolean defaultSubmit = true;
	private boolean isValidationRequired = false;
	private Map<String,String> labels;
	private JScript onSubmitScript;

	public Form(){
		addAttr("method", "POST").addAttr("action", "#");
	}
	
	public Form(CustomFormBean bean, String[] hiddens,String[] required,String[] orders,Map<String, String> labels)
			throws IllegalArgumentException, IllegalAccessException,
			InvocationTargetException {
		this();
		setHiddens(hiddens);
		setRequired(required);
		setLabels(labels);
		this.formBean = new FormBean(bean);

		//addAttr("method", "POST").addAttr("action", "#");
		Div row;

		// aggiungo una riga per i messaggi
		row = new Div();
		messages = new Span("");
		messages.setClassStyle("messages").setId("form-messages");
		row.append(messages);
		append(row);

		if(orders!=null && orders.length>0){
			for(String s: orders){				
				row = new Div();
				row.append(getInput(s, formBean.getInputText().get(s)));
				append(row);
				formBean.getInputText().remove(s);
			}
		}
		
		for (String key : formBean.getInputText().keySet()) {
			row = new Div();
			row.append(getInput(key, formBean.getInputText().get(key)));
			append(row);
		}
		
		row = new Div();
		row.append(getInput("beanid", bean.getBeanId()));
		append(row);

		submit = new Input(Input.TYPE_SUBMIT, "","Submit");
		submit.setClassStyle("btn");
		append(new Div().append(submit));
	}

	

	@Override
	public void onBeforeRender(String result) {	
		if (isDefaultSubmit() && getSubmit()!=null)
			//addScript(new JScript().on(
			addScript(getOnSubmitScript().on(	
					"click",
					"#" + getSubmit().getId(),
					new JFunction()
							.setBodyFunction("function(e){e.preventDefault(); alert('Default submit')}")));
		super.onBeforeRender(result);
	}

	/**
	 * This method add a new onSubmit listener to the form
	 * @param l
	 */
	public void onSubmit(Listener l) {
		setDefaultSubmit(false);
		append(new Input(Input.TYPE_HIDDEN, "listenerid", l.getId()));
		//AppManager.getAppState().addScript(new JScript().on(
		addScript(getOnSubmitScript().on(		
				"click",
				"#" + getSubmit().getId(),
				new JFunction()
						.setBodyFunction("function(e){e.preventDefault(); postAjaxForm('"+ this.getId() + "',"+isValidationRequired()+");}")));
	}

	/**
	 * This method check what is the result input field type.
	 * @param key
	 * @param string
	 * @return
	 */
	protected AbstractHtmlElement getInput(String key, String string) {
		if (Input.TYPE_HIDDEN.equalsIgnoreCase(getInputType(key))) {
			return new Div().append(
					new Input(getInputType(key), key, formBean.getInputText()
							.get(key)).setClassStyle(getClassStyle(key))).append(
					new Div().setClassStyle("jq-bl-div-clear"));
		}
		return new Div()
				.append(new Div(getLabels().get(key)!=null?getLabels().get(key):key).setClassStyle("jq-form-label"))
				.append(new Input(getInputType(key), key, formBean
						.getInputText().get(key))
						.setClassStyle(getClassStyle(key)))
				.append(new Div().setClassStyle("jq-bl-div-clear")).setClassStyle("control-group");
	}

	protected String getClassStyle(String key) {
		String result="jq-input";
		
		for(String s: required){
			if (s.equalsIgnoreCase(key)){
				setValidationRequired(true);
				//isValidationRequired=true;
				result+=" required ";
				break;
			}
		}
		
		return result;
	}

	/**
	 * This method check what is the result input field type.
	 * @param key
	 * @param string
	 * @return
	 */
	protected String getInputType(String key) {
		for (String hidden : hiddens) {
			if (hidden.equalsIgnoreCase(key))
				return Input.TYPE_HIDDEN;
		}
		return Input.TYPE_TEXT;
	}

	/**
	 * This class wrap a CustomFormBean. Form class used it for wite it's input field correctely. 
	 * @author zanottom
	 *
	 */
	public class FormBean implements Serializable {

		private static final long serialVersionUID = 2686299807073384190L;
		private CustomFormBean bean;
		private Map<String, String> inputText;

		public FormBean(CustomFormBean bean) throws IllegalArgumentException,
				IllegalAccessException, InvocationTargetException {
			this.setBean(bean);
			Method[] methods = bean.getClass().getMethods();
			//ordino i metodi
			
			sortMethods(methods);
			
			String value;
			for (int i = 0; i < methods.length; i++) {
				if (methods[i].getName().startsWith("get")
						&& isValidReturnType(methods[i].getReturnType())) {
					value = (String) methods[i].invoke(bean);
					if (value == null)
						value = "";
					String name = methods[i].getName().substring(3)
							.toLowerCase();
					getInputText().put(name, value);
				}
			}
		}

		private void sortMethods(Method[] methods) {
			Arrays.sort(methods, new Comparator<Method>() {

				@Override
				public int compare(Method o1, Method o2) {
					return o1.getName().compareToIgnoreCase(o2.getName());
					//return 0;
				}
			});
		}

		public Map<String, String> getInputText() {
			if (inputText == null)
				inputText = new LinkedHashMap<String, String>();
			return inputText;
		}

		public void setInputText(Map<String, String> inputText) {
			this.inputText = inputText;
		}

		public CustomFormBean getBean() {
			return bean;
		}

		public void setBean(CustomFormBean bean) {
			this.bean = bean;
		}

	}

	/**
	 * Every bean used in a From must extends this class
	 * @author zanottom
	 *
	 */
	public static class CustomFormBean implements Serializable{

		public String getBeanId(){
			return this.getClass().getName();
		}

	}

	public static class BeanFactory {

		public CustomFormBean getBean(Map<String, String[]> parameters,
				CustomFormBean bean) throws IllegalArgumentException,
				IllegalAccessException, InvocationTargetException {
			String[] values;
			Method[] methods = bean.getClass().getMethods();
			for (String s : parameters.keySet()) {
				for (Method m : methods) {
					if (m.getName().toLowerCase().matches("set" + s)) {
						values = parameters.get(s);
						if (values != null && values.length > 0
								&& values[0] != null) {
							m.invoke(
									bean,
									convertType(m.getParameterTypes()[0],
											values[0]));
						}
					}
				}
			}
			return bean;
		}

		@SuppressWarnings("rawtypes")
		protected Object convertType(Class cl, String val) {
			if (cl.equals(Integer.class))
				return new Integer(val);
			if (cl.equals(Long.class))
				return new Long(val);
			return val;
		}

	}

	/**
	 * This method check if a field return type is right for become an input field.
	 */
	public boolean isValidReturnType(Class<?> returnType) {
		return returnType.equals(String.class)
				|| returnType.equals(Integer.class)
				|| returnType.equals(Long.class);
	}

	/**
	 * This method populate list of hiddens field
	 * @param hiddens2
	 */
	public void setHiddens(String[] hiddens2) {
		hiddens = new ArrayList<String>();
		hiddens.add("beanid");
		if (hiddens2 != null && hiddens2.length > 0) {
			for (String s : hiddens2) {
				hiddens.add(s);
			}
		}
	}
	
	public void setRequired(String[] required2) {
		required = new ArrayList<String>();
		if (required2 != null && required2.length > 0) {
			setValidationRequired(true);
			for (String s : required2) {
				required.add(s);
			}
		}
		
	}
	
	public void setLabels(Map<String, String> labels2) {
		if(this.labels==null){
			labels=new HashMap<String,String>();
		}
		if(labels2!=null && labels2.size()>0)
			labels=labels2;
	}
	
	public Map<String, String> getLabels() {
		return labels;
	}

	public void setFormBean(CustomFormBean bean) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		this.formBean = new FormBean(bean);
	}
	
	public FormBean getFormBean(){
		return this.formBean;
	}

	public Input getSubmit() {
		return submit;
	}

	public void setSubmit(Input submit) {
		this.submit = submit;
	}

	public Span getMessages() {
		return messages;
	}

	public void setMessages(Span messages) {
		this.messages = messages;
	}

	public boolean isValidationRequired() {
		return isValidationRequired;
	}

	public void setValidationRequired(boolean isValidationRequired) {
		this.isValidationRequired = isValidationRequired;
	}

	public JScript getOnSubmitScript() {
		if(onSubmitScript==null){
			onSubmitScript=new JScript();
		}else{
			JScript tmp=new JScript();
			tmp.setId(onSubmitScript.getId());
			onSubmitScript=tmp;
		}
		return onSubmitScript;
	}

	public void setOnSubmitScript(JScript onSubmitScript) {
		this.onSubmitScript = onSubmitScript;
	}

	public boolean isDefaultSubmit() {
		return defaultSubmit;
	}

	public void setDefaultSubmit(boolean defaultSubmit) {
		this.defaultSubmit = defaultSubmit;
	}

}
